﻿using SafeLoad.NControl.Assist;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace SafeLoad.NControl
{
	/// <summary>
	/// NCurveFixed.xaml 的交互逻辑
	/// </summary>
	public partial class NCurveFixed : UserControl
	{
		public NCurveFixed()
		{
			InitializeComponent();
			LineList = new List<NCurveLine>();
		}

		public static readonly DependencyProperty TitleProperty =
			DependencyProperty.Register("Title", typeof(string), typeof(NCurveFixed), new PropertyMetadata(string.Empty,
				new PropertyChangedCallback(RenderUpdateByPropertyChanged)));
		/// <summary>
		/// 获取或设置图标的标题信息
		/// </summary>
		public string Title
		{
			get { return (string)GetValue(TitleProperty); }
			set { SetValue(TitleProperty, value); }
		}

		public static readonly DependencyProperty TitleFontSizeProperty =
			DependencyProperty.Register("TitleFontSize", typeof(int), typeof(NCurveFixed), new PropertyMetadata(12,
				new PropertyChangedCallback(RenderUpdateByPropertyChanged)));
		/// <summary>
		/// 标题大小
		/// </summary>
		public int TitleFontSize
		{
			get { return (int)GetValue(TitleFontSizeProperty); }
			set { SetValue(TitleFontSizeProperty, value); }
		}

		public static readonly DependencyProperty ColorLinesAndTextProperty =
			DependencyProperty.Register("ColorLinesAndText", typeof(Color), typeof(NCurveFixed), new PropertyMetadata(Colors.DimGray,
				new PropertyChangedCallback(RenderUpdateByPropertyChanged)));
		/// <summary>
		/// 获取或设置坐标轴及相关信息文本的颜色
		/// </summary>
		public Color ColorLinesAndText
		{
			get { return (Color)GetValue(ColorLinesAndTextProperty); }
			set { SetValue(ColorLinesAndTextProperty, value); }
		}

		public static readonly DependencyProperty ColorDashLinesProperty =
			DependencyProperty.Register("ColorDashLines", typeof(Color), typeof(NCurveFixed), new PropertyMetadata(Colors.Gray,
				new PropertyChangedCallback(RenderUpdateByPropertyChanged)));
		/// <summary>
		/// 获取或设置虚线的颜色
		/// </summary>
		public Color ColorDashLines
		{
			get { return (Color)GetValue(ColorDashLinesProperty); }
			set { SetValue(ColorDashLinesProperty, value); }
		}

		public static readonly DependencyProperty ValueMaxProperty =
		  DependencyProperty.Register("ValueMax", typeof(double), typeof(NCurveFixed), new PropertyMetadata(100d,
			new PropertyChangedCallback(RenderUpdateByPropertyChanged)));
		/// <summary>
		/// 纵坐标的最大值
		/// </summary>
		public double ValueMax
		{
			get { return (double)GetValue(ValueMaxProperty); }
			set { SetValue(ValueMaxProperty, value); }
		}

		public static readonly DependencyProperty ValueMinProperty =
		  DependencyProperty.Register("ValueMin", typeof(double), typeof(NCurveFixed), new PropertyMetadata(0d,
			new PropertyChangedCallback(RenderUpdateByPropertyChanged)));
		/// <summary>
		/// 纵坐标的最小值
		/// </summary>
		public double ValueMin
		{
			get { return (double)GetValue(ValueMinProperty); }
			set { SetValue(ValueMinProperty, value); }
		}

		public static readonly DependencyProperty ValueSegmentProperty =
		  DependencyProperty.Register("ValueSegment", typeof(int), typeof(NCurveFixed), new PropertyMetadata(5,
			new PropertyChangedCallback(RenderUpdateByPropertyChanged)));
		/// <summary>
		/// 纵坐标分段数
		/// </summary>
		public int ValueSegment
		{
			get { return (int)GetValue(ValueSegmentProperty); }
			set { SetValue(ValueSegmentProperty, value); }
		}

		public static readonly DependencyProperty TimeSegmentProperty =
			DependencyProperty.Register("TimeSegment", typeof(int), typeof(NCurveFixed), new PropertyMetadata(5,
				new PropertyChangedCallback(RenderUpdateByPropertyChanged)));
		/// <summary>
		/// 显示时间轴坐标标签的数量
		/// </summary>
		public int TimeSegment
		{
			get { return (int)GetValue(TimeSegmentProperty); }
			set { SetValue(TimeSegmentProperty, value); }
		}

		public static readonly DependencyProperty TimeRangeProperty =
			DependencyProperty.Register("TimeRange", typeof(int), typeof(NCurveFixed), new PropertyMetadata(600));
		/// <summary>
		/// 时间跨度（单位：秒）
		/// </summary>
		public int TimeRange
		{
			get { return (int)GetValue(TimeRangeProperty); }
			set { SetValue(TimeRangeProperty, value); }
		}

		public static readonly DependencyProperty TimeFormatProperty =
			DependencyProperty.Register("TimeFormat", typeof(string), typeof(NCurveFixed), new PropertyMetadata("HH:mm:ss",
				new PropertyChangedCallback(RenderUpdateByPropertyChanged)));
		/// <summary>
		/// Tag的格式 如：HH:mm:ss
		/// </summary>
		public string TimeFormat
		{
			get { return (string)GetValue(TimeFormatProperty); }
			set { SetValue(TimeFormatProperty, value); }
		}

		public static readonly DependencyProperty AlarmColorProperty =
			DependencyProperty.Register("AlarmColor", typeof(Color), typeof(NCurveFixed), new PropertyMetadata(Colors.Red));
		/// <summary>
		/// 报警颜色
		/// </summary>
		public Color AlarmColor
		{
			get { return (Color)GetValue(AlarmColorProperty); }
			set { SetValue(AlarmColorProperty, value); }
		}

		public static readonly DependencyProperty AlarmDiffValueProperty =
			DependencyProperty.Register("AlarmDiffValue", typeof(double), typeof(NCurveFixed), new PropertyMetadata(5.0d));
		/// <summary>
		/// 报警差值
		/// </summary>
		public double AlarmDiffValue
		{
			get { return (double)GetValue(AlarmDiffValueProperty); }
			set { SetValue(AlarmDiffValueProperty, value); }
		}

		/// <summary>
		/// 图形显示的开始时间
		/// </summary>
		public DateTime StartTime { get; private set; }

		/// <summary>
		/// 曲线列表
		/// </summary>
		private List<NCurveLine> LineList;

		/// <summary>
		/// 对照曲线数据
		/// </summary>
		private List<Tuple<DateTime, double>> baseLineValue;
		/// <summary>
		/// 对照曲线名称
		/// </summary>
		private string baseLineName;

		public static void RenderUpdateByPropertyChanged(DependencyObject dependency, DependencyPropertyChangedEventArgs e)
		{
			if (dependency is NCurveFixed curve)
			{
				curve.ControlUpdate();
			}
		}

		public void ResetCurve()
		{
			StartTime = DateTime.MinValue;
			LineList = new List<NCurveLine>();
			baseLineValue = null;
			baseLineName = string.Empty;
			ControlUpdate();
		}

		protected override void OnRender(DrawingContext drawingContext)
		{
			base.OnRender(drawingContext);
			ControlUpdate();
		}


		public void AddCurveLine(string lineName, Color lineColor, double lineThickness = 1.5d)
		{
			if (LineList.Count(l => l.LineName.Equals(lineName)) > 0)
			{
				throw new Exception($"已存在名为【{lineName}】的曲线");
			}

			NCurveLine curveLine = new NCurveLine(canvas1, lineName, lineColor) { LineThickness = lineThickness };
			curveLine.AlarmColor = AlarmColor;
			curveLine.AlarmInfo = "报警";
			curveLine.CalcPointFunc = new NCurveLine.CalcLinePoint(CalcLinePoint);
			curveLine.CalcAlarmInfoFunc = new NCurveLine.CalcAlarmInfoPoint(CalcAlarmInfoPoint);
			LineList.Add(curveLine);
			ControlUpdate();
		}


		public bool AddCurveData(string lineName, DateTime time, double value)
		{
			NCurveLine baseLineCurve = LineList.First(line => line.LineName.Equals(lineName));

			if (time == DateTime.MinValue)
			{
				return false;
			}

			if (StartTime == DateTime.MinValue)
			{
				StartTime = DateTime.Parse(time.ToString("yyyy-MM-dd HH:mm:ss"));
				ControlUpdate();
			}

			if (time < StartTime)
			{
				return false;
			}
			else if (time >= StartTime.AddSeconds(TimeRange))
			{
				//左移时间轴
				TimeLeftShift(time);
			}

			bool alarm = false;
			if (baseLineValue != null && baseLineValue.Count > 0)
			{
				//计算标准值与实际值的差别
				if (time >= baseLineValue.First().Item1 && time <= baseLineValue.Last().Item1)
				{
					//找到相距最近的一条数据
					for (int i = 1; i < baseLineValue.Count; i++)
					{
						Tuple<DateTime, double> preTuple = baseLineValue[i - 1];
						Tuple<DateTime, double> nextTuple = baseLineValue[i];

						if (time >= preTuple.Item1 && time <= nextTuple.Item1)
						{
							double delta = (time - preTuple.Item1).TotalSeconds / (nextTuple.Item1 - preTuple.Item1).TotalSeconds;
							double baseval = preTuple.Item2 + (nextTuple.Item2 - preTuple.Item2) * delta;
							alarm = Math.Abs(baseval - value) > AlarmDiffValue;
							break;
						}
					}
				}
			}
			baseLineCurve.AddData(time, value, alarm);
			return true;
		}

		/// <summary>
		/// 设置基础曲线，用于对比
		/// </summary>
		/// <param name="lineName">对比线名称</param>
		/// <param name="lineColor">对比线颜色</param>
		/// <param name="lineThickness">对比线宽度</param>
		/// <param name="valueList">对比线数值</param>
		public void SetBaseLine(string lineName, Color lineColor, double lineThickness, List<Tuple<DateTime, double>> valueList)
		{
			//删除原来的基础曲线
			RemoveBaseLine();

			if (LineList.Count(l => l.LineName.Equals(lineName)) > 0)
			{
				throw new Exception($"已存在名为【{lineName}】的曲线");
			}

			//将基础曲线添加到第一条
			NCurveLine curveLine = new NCurveLine(canvas1, lineName, lineColor) {
				LineThickness = lineThickness
			};
			curveLine.CalcPointFunc = new NCurveLine.CalcLinePoint(CalcLinePoint);
			curveLine.CalcAlarmInfoFunc = new NCurveLine.CalcAlarmInfoPoint(CalcAlarmInfoPoint);
			LineList.Insert(0, curveLine);
			baseLineName = lineName;
			baseLineValue = valueList;
			ControlUpdate();
		}

		private void SetBaseLineData()
		{
			if (string.IsNullOrEmpty(baseLineName))
			{
				return;
			}

			NCurveLine baseLineCurve = LineList.Where(l => l.LineName.Equals(baseLineName)).Select(l => l).FirstOrDefault();
			if (baseLineCurve == null)
			{
				return;
			}

			if (StartTime != DateTime.MinValue)
			{
				DateTime endTime = StartTime.AddSeconds(TimeRange);
				//先将baseLineCurve中所有数据都移除
				baseLineCurve.LeftShift(DateTime.MaxValue);
				foreach (Tuple<DateTime, double> value in baseLineValue)
				{
					if (value.Item1 >= StartTime && value.Item1 < endTime)
					{
						baseLineCurve.AddData(value.Item1, value.Item2);
					}
				}
			}
		}

		/// <summary>
		/// 清除对照曲线
		/// </summary>
		public void RemoveBaseLine()
		{
			if (!string.IsNullOrEmpty(baseLineName))
			{
				if (LineList.Count(line => line.LineName.Equals(baseLineName)) > 0)
				{
					NCurveLine baseLineCurve = LineList.First(l => l.LineName.Equals(baseLineName));
					LineList.Remove(baseLineCurve);
				}
				baseLineName = null;
				baseLineValue = null;
			}
			ControlUpdate();
		}

		private void TimeLeftShift(DateTime newTime)
		{
			while (newTime >= StartTime.AddSeconds(TimeRange))
			{
				if (newTime < StartTime.AddSeconds(TimeRange * 1.1))
				{
					StartTime = StartTime.AddSeconds(Math.Round(TimeRange * 0.1));
				}
				else if (newTime < StartTime.AddSeconds(TimeRange * 1.2))
				{
					StartTime = StartTime.AddSeconds(Math.Round(TimeRange * 0.2));
				}
				else if (newTime < StartTime.AddSeconds(TimeRange * 1.5))
				{
					StartTime = StartTime.AddSeconds(Math.Round(TimeRange * 0.5));
				}
				else
				{
					StartTime = DateTime.Parse(newTime.ToString("yyyy-MM-dd HH:mm:ss"));
				}
			}

			foreach (NCurveLine line in LineList)
			{
				line.LeftShift(StartTime);
			}

			ControlUpdate();
		}

		protected readonly int OffectLeft = 50;
		protected readonly int OffectRight = 30;
		protected readonly int OffectUp = 25;
		protected readonly int OffectDown = 25;

		private void ControlUpdate()
		{
			int widthTotal = (int)ActualWidth + 1;
			int heightTotal = (int)ActualHeight + 1;

			if (widthTotal < 120 || heightTotal < 60) return;

			SetBaseLineData();

			canvas1.Children.Clear();

			Point mainPoint = new Point(OffectLeft - 1, heightTotal - OffectDown);
			Point rightPoint = new Point(widthTotal - OffectRight, heightTotal - OffectDown);
			Point topPoint = new Point(OffectLeft - 1, OffectUp - 8);

			WpfUtils.DrawLine(canvas1, topPoint, mainPoint, ColorLinesAndText, 1d);
			WpfUtils.DrawLine(canvas1, mainPoint, rightPoint, ColorLinesAndText, 1d);
			WpfUtils.FillTriangle(canvas1, topPoint, ColorLinesAndText, 4, Assist.GraphDirection.Upward);

			//绘制曲线名称
			if (LineList.Count > 1)
			{
				double xStart = OffectLeft + 10;
				foreach (NCurveLine line in LineList)
				{
					WpfUtils.DrawLine(canvas1, xStart, 11, xStart + 30, 11, line.LineColor, 1d);
					WpfUtils.DrawEllipse(canvas1, xStart + 8, 4, 14, 14, line.LineColor, 1d);
					WpfUtils.DrawString(canvas1, line.LineName, line.LineColor, xStart + 35, 0, 120, 30, HorizontalAlignment.Left, VerticalAlignment.Center);
					xStart += 100;
				}
			}

			//绘制刻度线，以及刻度文本
			for (int i = 0; i <= ValueSegment; i++)
			{
				double valLeft = i * (ValueMax - ValueMin) / ValueSegment + ValueMin;
				double paintTmp = NHelper.ComputePaintLocationY(ValueMax, ValueMin, (heightTotal - OffectUp - OffectDown), valLeft) + OffectUp;
				WpfUtils.DrawLine(canvas1, OffectLeft - 4, paintTmp, OffectLeft - 1, paintTmp, ColorLinesAndText, 1d);
				WpfUtils.DrawString(canvas1, valLeft.ToString(), ColorLinesAndText, 0, paintTmp - 20, OffectLeft - 4, 40, HorizontalAlignment.Right, VerticalAlignment.Center);
				if (i > 0)
				{
					WpfUtils.DrawLine(canvas1, OffectLeft, paintTmp, widthTotal - OffectRight, paintTmp, ColorDashLines, 1d, new double[] { 5d, 5d });
				}
			}

			if (StartTime != DateTime.MinValue)
			{
				DateTime endTime = StartTime.AddSeconds(TimeRange);
				DrawTimeTag(StartTime);
				DrawTimeTag(endTime);
				double timeStep = Math.Round((double)TimeRange / TimeSegment);
				DateTime tagTime = StartTime.AddSeconds(timeStep);
				while (tagTime < endTime)
				{
					DrawTimeTag(tagTime);
					tagTime = tagTime.AddSeconds(timeStep);
				}

				//画曲线
				//是否将所有点都画出来的标记
				foreach (NCurveLine line in LineList)
				{
					line.Repaint();
				}
			}
		}

		private void DrawTimeTag(DateTime time)
		{
			double curveWidth = ActualWidth - OffectLeft - OffectRight;
			double timeOffset = Math.Round((time - StartTime).TotalSeconds);
			double x = OffectLeft + curveWidth * (timeOffset / TimeRange);
			double yBottom = ActualHeight - OffectDown + 1;
			double yTop = OffectUp;
			WpfUtils.DrawString(canvas1, time.ToString(TimeFormat), ColorLinesAndText, x - 100, yBottom + 1, 200, ActualHeight - yBottom, HorizontalAlignment.Center, VerticalAlignment.Top);
			if (time != StartTime)
			{
				WpfUtils.DrawLine(canvas1, x, yBottom, x, yTop, ColorDashLines, 1d, new double[] { 5d, 5d });
			}
		}

		private Point CalcLinePoint(DateTime time, double value)
		{
			double curveWidth = ActualWidth - OffectLeft - OffectRight;
			double timeOffset = (time - StartTime).TotalSeconds;
			double x = OffectLeft + curveWidth * (timeOffset / TimeRange);
			double yBottom = ActualHeight - OffectDown;
			double curveHeight = yBottom - OffectUp;
			double y = yBottom - ((value - ValueMin) / (ValueMax - ValueMin)) * curveHeight;
			return new Point(x, y);
		}

		private Point CalcAlarmInfoPoint(DateTime time, double value, int fontSize)
		{
			double curveWidth = ActualWidth - OffectLeft - OffectRight;
			double timeOffset = (time - StartTime).TotalSeconds;
			double x = OffectLeft + curveWidth * (timeOffset / TimeRange);
			double yBottom = ActualHeight - OffectDown;
			double curveHeight = yBottom - OffectUp;
			double y = yBottom - ((value - ValueMin) / (ValueMax - ValueMin)) * curveHeight;

			if (y + (fontSize * 2) >= yBottom)
			{
				y -= fontSize * 2;
			}

			return new Point(x, y);
		}

	}
}
